package common.managers;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.support.v4.util.LruCache;
import android.util.Log;
import android.util.SparseArray;
import android.widget.ImageView;

public abstract class AbstractImageManager implements IImageManager{
	
	public static final String TAG = AbstractImageManager.class.getSimpleName();

	private LruCache<String, Bitmap> memoryCache;
	private SparseArray<String> syncImageView;
	private Handler handler;
	private ExecutorService executorService;
	private Exception mError;
	private List<Callback> queue;
	private Context context;

	public interface Callback {
		String getUrl();

		void onSuccess(Bitmap bm);

		void onError(Exception e);
	}
	
	public AbstractImageManager(Context context, int sizeMemoryCache, int poolSize) {
		this.context = context;
		memoryCache = new LruCache<String, Bitmap>(sizeMemoryCache) {
			@Override
			protected int sizeOf(String key, Bitmap value) {
				int size = value.getWidth() * value.getHeight() * 4;
				return size;
			}

		};
		syncImageView = new SparseArray<String>();
		handler = new Handler();
		queue = Collections.synchronizedList(new ArrayList<Callback>());
		executorService = Executors.newFixedThreadPool(poolSize);
	}
	
	public void loadImage(final String url, final ImageView imageView) {
		if (url == null || url.equals("") || imageView == null) {
			return;
		}
		Bitmap bitmap = getBitmapFromMemory(url);
		if (bitmap != null) {
			imageView.setImageBitmap(bitmap);
			return;
		} else {
			imageView.setImageDrawable(null);
		}

		syncImageView.put(imageView.hashCode(), url);

		queue.add(0, new Callback() {

			@Override
			public void onSuccess(Bitmap bm) {
				if (mError != null) {
					onError(mError);
					mError = null;
					return;
				}
				if (bm != null) {
					if (getUrl().equals(syncImageView.get(imageView.hashCode()))) {
						imageView.setImageBitmap(bm);
						syncImageView.delete(imageView.hashCode());
					}
					addBitmapToMemCache(getUrl(), bm);
				}
				execute();
			}

			@Override
			public void onError(Exception e) {
				Log.e(TAG, "Error: " + e);
			}

			@Override
			public String getUrl() {
				return url;
			}
		});
		execute();
	}

	private void execute() {
		executorService.execute(new Runnable() {
			@Override
			public void run() {

				final Callback callback;

				synchronized (queue) {
					if (queue.isEmpty()) {
						return;
					}
					callback = queue.remove(0);
				}

				Bitmap bitmap = null;
				String url = callback.getUrl();
				String path = getPathCacheDir(url);
				if (new File(path).exists()) {
					bitmap = loadBitmapFromFile(path);
				} else {
					try {
						bitmap = loadBitmapFromWeb(url);
					} catch (final IOException e) {
						mError = e;
					}
				}

				final Bitmap bm = bitmap;
				handler.post(new Runnable() {

					@Override
					public void run() {
						callback.onSuccess(bm);
					}
				});
			}
		});
	}

	private Bitmap getBitmapFromMemory(String url) {
		return memoryCache.get(url);
	}

	private void addBitmapToMemCache(String url, Bitmap bitmap) {
		if (getBitmapFromMemory(url) == null) {
			memoryCache.put(url, bitmap);
		}
	}
	
	private Bitmap loadBitmapFromFile(String path) {
		return BitmapFactory.decodeFile(path);
	}

	private Bitmap loadBitmapFromWeb(final String url) throws IOException {
		final Bitmap bitmap = loadBitmap(url);
		new Thread(new Runnable() {

			@Override
			public void run() {
				addBitmapToFileCache(getPathCacheDir(url), bitmap);
			}
		}).start();
		return bitmap;
	}


	private void addBitmapToFileCache(String path, Bitmap bitmap) {
		if (bitmap == null || path == null) {
			Log.d(TAG, "Write to file bitmap or path = null ");
			return;
		}
		OutputStream out = null;
		try {
			out = new BufferedOutputStream(new FileOutputStream(path));
			bitmap.compress(Bitmap.CompressFormat.JPEG, 90, out);
		} catch (FileNotFoundException e) {
			this.mError = e;
		} finally {
			if (out != null) {
				try {
					out.close();
				} catch (IOException e) {
					this.mError = e;
				}
			}
		}
	}

	protected Context getContext() {
		return context;
	}
	
	protected abstract Bitmap loadBitmap(String url) throws IOException;

	protected abstract String getPathCacheDir(String url);
}
